#include "CentralCache.hpp"
#include "PageCache.hpp"

CentralCache CentralCache::_instance;

Span *CentralCache::GetOneSpan(SpanList &list, size_t size)
{
    Span *it = list.Begin();
    while (it != list.End())
    {
        if (it->_freelist != nullptr)
        {
            return it;
        }
        else
        {
            it = it->_next;
        }
    }
    //我们需要先解桶锁，才能执行下面的操作
    list._mutex.unlock();
    // 走到这里说明当前桶下面的span双链表全部为空
    // 因此需要找下一层page cache申请内存
    PageCache::GetInstance()->_mutex.lock();
    //在PageCache当中申请一个span
    Span*span=PageCache::GetInstance()->GetPageSpan(SizeClass::CalculatePageSize(size));
    span->_isUse=true;
    PageCache::GetInstance()->_mutex.unlock();
    //此时我们已经申请到了一个span，接下来我们需要对这个span进行切分
    //计算当前span的头和尾
    char *start = (char *)((size_t)span->_pageid << BIT_SHIFT);
    PAGE_ID byteSize = span->_pagenum << BIT_SHIFT;
    char *end = start + byteSize;
    //进行大块内存切分操作
    span->_freelist=start;
    start+=size;
    void*tail=span->_freelist;
    while(start<end)
    {
        NextObj(tail)=start;
        tail=NextObj(tail);
        start+=size;
    }
    NextObj(tail)=nullptr;
    //需要进入桶里面将span插入时，加锁
    list._mutex.lock();
    list.PushFront(span);
    return span; 
}

size_t CentralCache::AllocRangeMemory(void *&start, void *&end, size_t blockNum, size_t size)
{
    // 首先我们应该根据内存大小size找到下标的索引，然后遍历这个桶下面的Span跨度双链表，然后在这个桶下面找到一个非空的Span，记录这个非空span的地址
    size_t index = SizeClass::Index(size);
    _spanlist[index]._mutex.lock();
    Span *span = GetOneSpan(_spanlist[index], size);
    // 此时我们已经获取了一个非空的span，接下来我们需要遍历这个span下面的自由链表
    // 如果能够满足blocknum的需求就满足，不能满足则有多少就给多少
    start = span->_freelist;
    end = start;
    size_t num = 1;
    while (num < blockNum && NextObj(end) != nullptr)
    {
        num++;
        end = NextObj(end);
    }
    span->_freelist = NextObj(end);
    NextObj(end) = nullptr;
    span->_useCount+=num;
    _spanlist[index]._mutex.unlock();
    return num;
}

// 回收ThreadCache交给Central的内存
void CentralCache::RecycleThreadCacheMemory(void *start, size_t size)
{
    // 首先根据内存大小，算到在哪个桶下面
    size_t index = SizeClass::Index(size);
    // 如果start指向的内存不为空，则进入循环
    // 执行回收流程
    _spanlist[index]._mutex.lock();
    while (start)
    {
        void *next = NextObj(start);
        // 将每一个节点依次链入到对应的span跨度下面
        //那么我们如何得到对应的span呢？、
        //方法一：我们可以重复前面申请内存的逻辑，找到对应的index，然后依次头插
        //但是仔细观察我们可以发现，page cache和Central cache的hash桶下面挂的也是span，只不过他们的映射规则不同
        //当central cache当中回收的内存数量达到一定的限度时，会将自己的内存交给page cache，此时我们还要查找page cache对应的index
        //那么这就会导致相同的逻辑在不同的块当中重复出现，那么有没有其他的解决方法呢？
        //方法二：在page cache当中建立页号pageid和span的映射，这样我们就可以通过页号找到对应的span
        Span *span =PageCache::GetInstance()->GetObjectToSpan(start);
        // 依次头插进入
        NextObj(start)=span->_freelist;
        span->_freelist=start;
        //此时我们发现一个问题，如果centra cache一直回收thread cache的空间，那么central cache下面的链表会非常之多
        //所以当central cache的链表太长时我们应该继续向下释放空间
        //那么什么时候才能执行向page cache释放空间呢？
        //我们在span当中增加一个参数usecount，表示thread cache向central cache申请空间的引用计数
        span->_useCount--;
        //如果引用计数减到0，执行向pagecache回收内存的机制
        if(span->_useCount==0)
        {
            _spanlist[index].Erase(span);
            span->_freelist=nullptr;
            span->_next=nullptr;
            span->_prev=nullptr;

            // 释放span给page cache时，使用page cache的锁就可以了
            // 这时把桶锁解掉
            _spanlist[index]._mutex.unlock();
            PageCache::GetInstance()->_mutex.lock();
            PageCache::GetInstance()->RecycleCentralCacheMemory(span);
            PageCache::GetInstance()->_mutex.unlock();
        }
        start=next;
    }
    _spanlist[index]._mutex.unlock();
}